package com.example;

import org.apache.http.*;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.*;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.*;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.http.io.HttpMessageParser;
import org.apache.http.io.HttpMessageParserFactory;
import org.apache.http.io.HttpMessageWriterFactory;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.message.BasicLineParser;
import org.apache.http.message.LineParser;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.util.Arrays;

/**
 * 在进行请求的时候, 先进性配置, 在进行请求
 */
public class HttpClientConf {

    public static void main(String[] args) {

        //使用自定义消息解析器/编写器自定义从数据流解析和输出HTTP消息的方式。
        HttpMessageParserFactory<HttpResponse> responseFactory = new DefaultHttpResponseParserFactory(){
            @Override
            public HttpMessageParser<HttpResponse> create(SessionInputBuffer buffer, MessageConstraints constraints) {

                LineParser lineParser = new BasicLineParser(){
                    @Override
                    public Header parseHeader(CharArrayBuffer buffer) throws ParseException {
                        return super.parseHeader(buffer);
                    }
                };
                return new DefaultHttpResponseParser(buffer,lineParser, DefaultHttpResponseFactory.INSTANCE,constraints){
                    @Override
                    protected boolean reject(CharArrayBuffer line, int count) {
                        //尝试无限地忽略状态行前面的所有垃圾
                        return false;
                    }
                };
            }
        };

        HttpMessageWriterFactory<HttpRequest> requestFactory = new DefaultHttpRequestWriterFactory();
        //使用自定义连接工厂自定义传出HTTP连接的初始化过程。除了标准连接配置参数之外，
        // HTTP连接工厂还可以定义单个连接使用的消息解析器/编写器例程。
        HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connectionFactory = new ManagedHttpClientConnectionFactory(requestFactory,responseFactory);
        //完全初始化后的客户端HTTP连接对象可以绑定到任意网络套接字。网络套接字的初始化过程、与远程地址的连接以及与本地地址的绑定都由连接套接字工厂控制。

        //--------------------
        //安全连接的SSL上下文可以基于系统或应用程序特定的属性创建。
        final SSLContext sslContext = SSLContexts.createDefault();

        //为支持的协议方案创建自定义连接套接字工厂的注册表。
        Registry<ConnectionSocketFactory> registryTable = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https",new SSLConnectionSocketFactory(sslContext))
                .build();

        //使用自定义DNS解析程序覆盖系统DNS解析。
        DnsResolver dnsResolver = new SystemDefaultDnsResolver(){
            @Override
            public InetAddress[] resolve(String host) throws UnknownHostException {
                if (host.equalsIgnoreCase("myhost")){
                    return new InetAddress[]{InetAddress.getByAddress(new byte[]{127,0,0,1})};
                }else {
                    return super.resolve(host);
                }
            }
        };

        //使用自定义配置创建连接管理器。
        final PoolingHttpClientConnectionManager managedConnection =
                new PoolingHttpClientConnectionManager(registryTable, connectionFactory, dnsResolver);

        // Create socket configuration
        final SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
        //将连接管理器配置为在默认情况下或为特定主机使用套接字配置
        managedConnection.setDefaultSocketConfig(socketConfig);
        managedConnection.setSocketConfig(new HttpHost("www.baidu.com",80),socketConfig);
        //不活动1秒后验证连接
        managedConnection.setValidateAfterInactivity(1000);
        //创建消息约束
        final MessageConstraints messageConstraints = MessageConstraints.custom()
                .setMaxHeaderCount(200)
                .setMaxLineLength(2000)
                .build();
        // Create connection configuration
        final ConnectionConfig connectionConfig = ConnectionConfig.custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .setMessageConstraints(messageConstraints)
                .build();
        //将连接管理器配置为在默认情况下或为特定主机使用连接配置。
        managedConnection.setDefaultConnectionConfig(connectionConfig);
        managedConnection.setConnectionConfig(new HttpHost("www.baidu.com",80),ConnectionConfig.DEFAULT);
        //为可保留在池中或由连接管理器租用的持久连接配置总最大或每条路由限制
        managedConnection.setMaxTotal(100);
        managedConnection.setDefaultMaxPerRoute(10);
        managedConnection.setMaxPerRoute(new HttpRoute(new HttpHost("www.baidu.com",80)),20);

        // Use custom cookie store if necessary
        final BasicCookieStore basicCookieStore = new BasicCookieStore();
        // Use custom credentials provider if necessary.
        final BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
        // Create global request configuration
        final RequestConfig requestConfig = RequestConfig.custom()
                .setCookieSpec(CookieSpecs.DEFAULT)
                .setExpectContinueEnabled(true)
                .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
                .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
                .build();
        // Create an HttpClient with the given custom dependencies and configuration.
        try(final CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(managedConnection)
                .setDefaultCookieStore(basicCookieStore)
                .setDefaultCredentialsProvider(basicCredentialsProvider)
                .setProxy(new HttpHost("myProxy", 80))
                .setDefaultRequestConfig(requestConfig)
                .build()) {

            final HttpGet httpGet = new HttpGet("https://www.baidu.com");
            //可以在请求级别重写请求配置。它们将优先于客户端级别的集合
            final RequestConfig cliRequestConfig = RequestConfig.copy(requestConfig)
                    .setSocketTimeout(5000)
                    .setConnectTimeout(5000)
                    .setConnectionRequestTimeout(5000)
                    .setProxy(new HttpHost("myProxy", 80))
                    .build();
            httpGet.setConfig(cliRequestConfig);
            //可以在本地自定义执行上下文
            final HttpClientContext context = HttpClientContext.create();
            //上下文属性集本地上下文级别将优先于在客户端级别设置的属性
            context.setCookieStore(basicCookieStore);
            context.setCredentialsProvider(basicCredentialsProvider);
            try(final CloseableHttpResponse response = httpClient.execute(httpGet, context)){

                System.out.println(response.getStatusLine());
                System.out.println(EntityUtils.toString(response.getEntity()));

                //一旦请求被执行，本地上下文就可以用来检查受请求执行影响的更新状态和各种对象。
                // Last executed request
                context.getRequest();
                // Execution route
                context.getHttpRoute();
                // Target auth state
                context.getTargetAuthState();
                // Proxy auth state
                context.getProxyAuthState();
                // Cookie origin
                context.getCookieOrigin();
                // Cookie spec used
                context.getCookieSpec();
                // User security token
                context.getUserToken();

            }catch (Exception e){
                e.printStackTrace();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
